package nl.utwente.ewi.hmi.multitouch.drivers.calibrate;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import java.util.List;

import javax.media.jai.PerspectiveTransform;
import javax.swing.JFrame;
import javax.swing.JPanel;

import nl.utwente.ewi.hmi.multitouch.Touch;
import nl.utwente.ewi.hmi.multitouch.TouchDevice;
import nl.utwente.ewi.hmi.multitouch.TouchDeviceAdapter;
import nl.utwente.ewi.hmi.multitouch.event.TouchAdapter;
import nl.utwente.ewi.hmi.multitouch.event.TouchEvent;
import nl.utwente.ewi.hmi.multitouch.io.TouchDeviceException;

public class DefaultManualCalibrator implements TouchDeviceCalibrator {

	private Rectangle2D targetArea = null;

	private static final Rectangle2D UNIT_AREA = new Rectangle2D.Float(-1, -1, 2, 2);
	
	public DefaultManualCalibrator(Rectangle2D targetArea) {
		this.targetArea = targetArea;
	}

	public DefaultManualCalibrator() {
	}

	public static DefaultManualCalibrator getUnitCalibrator() {
		return new DefaultManualCalibrator(UNIT_AREA);
	}

	public AffineTransform calibrate(TouchDevice device) {
		CalibrateFrame frame = new CalibrateFrame(device, targetArea);
		AffineTransform result = frame.getTransform();
		
		frame.setVisible(false);
		frame.dispose();

		return result;
	}
	
	private static class CalibrateFrame extends JFrame {
		
		private List<Point2D> sourcePoints;
		
		private List<Point2D> targetPoints;

		private BufferedImage image = null;

		private Object lock = new Object();
		
		private TouchDevice device = null;
		
		private AffineTransform transform = null;
		
		private Rectangle2D targetArea = null;

		public CalibrateFrame(TouchDevice device, Rectangle2D targetArea) {
			this.device = device;
			System.out.println("right here");
			device.addTouchDeviceListener(new TouchDeviceAdapter() {
				@Override
				public void touchRegistered(TouchDevice touchDevice, Touch touch) {
					CalibrateFrame.this.touchRegistered(touch);
				}
			});
			
			sourcePoints = new LinkedList<Point2D>();
			targetPoints = new LinkedList<Point2D>();
			

			
			this.setUndecorated(true);
			
			GraphicsDevice graphicsDevice = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
			
			if(targetArea == null) {
				targetArea = new Rectangle2D.Float(0, 0, graphicsDevice.getDisplayMode().getWidth(), graphicsDevice.getDisplayMode().getHeight());
			}
			this.targetArea = targetArea;

			image = new BufferedImage(graphicsDevice.getDisplayMode().getWidth(), graphicsDevice.getDisplayMode().getHeight(), BufferedImage.TYPE_INT_ARGB);

			sourcePoints.add(new Point2D.Float(0.25f, 0.25f));
			sourcePoints.add(new Point2D.Float(0.25f , 0.75f));
			sourcePoints.add(new Point2D.Float(0.75f , 0.75f));
			sourcePoints.add(new Point2D.Float(0.75f , 0.25f));
			
			this.getContentPane().setLayout(new BorderLayout());
			this.getContentPane().add(new JPanel() {
				@Override
				public void paint(Graphics g) {
					g.drawImage(image, 0, 0, this.getWidth(), this.getHeight(), 0, 0, image.getWidth(), image.getHeight(), null);
				}
			});
			this.getContentPane().setPreferredSize(new Dimension(image.getWidth(), image.getHeight()));
			this.pack();

			
			graphicsDevice.setFullScreenWindow(this);
			this.setVisible(true);
			this.acceptTouch();
			
			this.addKeyListener(new KeyAdapter() {
				@Override
				public void keyPressed(KeyEvent e) {
					if(e.getKeyCode() == KeyEvent.VK_ENTER) {
							if(transform != null) {
							synchronized(lock) {
								lock.notifyAll();
							}
						}
					}
					if(e.getKeyCode() == KeyEvent.VK_ESCAPE) {
						if(!targetPoints.isEmpty()) {
							transform = null;
							targetPoints.clear();
							acceptTouch();
						}
					}
				}
			});

		}
		
		void acceptTouch() {
			Graphics2D g = (Graphics2D) image.getGraphics();
			g.setColor(Color.black);
			g.fillRect(0, 0, image.getWidth(), image.getHeight());

			if(sourcePoints.size() == targetPoints.size()) {

				double left = targetArea.getX() + 0.25f * targetArea.getWidth();
				double right = targetArea.getX() + 0.75f * targetArea.getWidth();
				double top = targetArea.getY() + 0.25f * targetArea.getHeight();
				double bottom = targetArea.getY() + 0.75f * targetArea.getHeight();
				
				transform = new FacadeTransform(PerspectiveTransform.getQuadToQuad(targetPoints.get(0).getX(), targetPoints.get(0).getY(),targetPoints.get(1).getX(), targetPoints.get(1).getY(),targetPoints.get(2).getX(), targetPoints.get(2).getY(), targetPoints.get(3).getX(), targetPoints.get(3).getY(), left, top, left, bottom,right, bottom, right, top));
				this.repaint();
				return;
			}

			g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			
			g.setColor(Color.green);
			Point2D sourcePoint = (Point2D)sourcePoints.get(targetPoints.size()).clone();
			sourcePoint.setLocation(sourcePoint.getX() * image.getWidth(), sourcePoint.getY() * image.getHeight());
			
			double size = 0.1d * image.getHeight();
			
			Ellipse2D ellipse = new Ellipse2D.Double(sourcePoint.getX() - (size/2), 
													sourcePoint.getY() - (size/2), size, size);
			
			g.fill(ellipse);
			this.repaint();
		}
		
		void touchRegistered(final Touch touch) {
			System.out.println("REGISTERED");
			final double size = 0.01d * getHeight();
			
			final Graphics2D g = (Graphics2D) image.getGraphics();
			
			
			if(targetPoints.size() < sourcePoints.size()) {
				Point2D point = new Point2D.Float();
//				transform.transform(touch.getOrigin(), point);

				Ellipse2D ellipse = new Ellipse2D.Double(point.getX() - (size/2), 
						point.getY() - (size/2), size, size);

				g.setColor(Color.red);
				g.fill(ellipse);
				repaint();

				touch.addTouchListener(new TouchAdapter() {
					@Override
					public void touchReleased(TouchEvent event) {
						targetPoints.add(touch.getOriginStart());
						acceptTouch();
					}
					
					@Override
					public void touchMoved(TouchEvent event) {
						Point2D point = new Point2D.Float();
//						transform.transform(event.getSource().getOrigin(), point);

						Ellipse2D ellipse = new Ellipse2D.Double(point.getX() - (size/2), 
								point.getY() - (size/2), size, size);

						g.setColor(Color.red);
						g.fill(ellipse);
						repaint();
					}
				});
				
				return;
			}

			
			touch.addTouchListener(new TouchAdapter() {
				
				GeneralPath path = new GeneralPath();
				
				@Override
				public void touchMoved(TouchEvent event) {
					Point2D point = new Point2D.Float();
					transform.transform(event.getSource().getOrigin(), point);

					Ellipse2D ellipse = new Ellipse2D.Double(point.getX() - (size/2), 
							point.getY() - (size/2), size, size);

					g.setColor(Color.blue);
					g.fill(ellipse);
					repaint();

				}

				@Override
				public void touchReleased(TouchEvent event) {
					event.getSource().removeTouchListener(this);
				}
				
				
			});

			
		}
		
		AffineTransform getTransform() {
			synchronized(lock) {
				try {
					lock.wait();
				} catch (InterruptedException e) {
				}
			}
			return this.transform;
		}
	}
	
	private static class FacadeTransform extends AffineTransform {
		PerspectiveTransform usuper;

		public FacadeTransform(PerspectiveTransform transform) {
			this.usuper = transform;
		}
		
		@Override
		public void concatenate(AffineTransform Tx) {
			usuper.concatenate(Tx);
		}

		@Override
		public AffineTransform createInverse()
				throws NoninvertibleTransformException {
			try {
				return new FacadeTransform(usuper.createInverse());
			} catch (CloneNotSupportedException e) {
			}
			return null;
		}
		
		@Override
		public Shape createTransformedShape(Shape src) {
			return null;
		}
		
		@Override
		public void transform(double[] srcPts, int srcOff, double[] dstPts,
				int dstOff, int numPts) {
			usuper.transform(srcPts, srcOff, dstPts, dstOff, numPts);
		}
		
		@Override
		public void transform(double[] srcPts, int srcOff, float[] dstPts,
				int dstOff, int numPts) {
			usuper.transform(srcPts, srcOff, dstPts, dstOff, numPts);
		}
		
		@Override
		public void transform(float[] srcPts, int srcOff, double[] dstPts,
				int dstOff, int numPts) {
			usuper.transform(srcPts, srcOff, dstPts, dstOff, numPts);
		}
		
		@Override
		public void transform(float[] srcPts, int srcOff, float[] dstPts,
				int dstOff, int numPts) {
			usuper.transform(srcPts, srcOff, dstPts, dstOff, numPts);
		}
		
		@Override
		public Point2D transform(Point2D ptSrc, Point2D ptDst) {
			return usuper.transform(ptSrc, ptDst);
		}
		
		@Override
		public void transform(Point2D[] ptSrc, int srcOff, Point2D[] ptDst,
				int dstOff, int numPts) {
			usuper.transform(ptSrc, srcOff, ptDst, dstOff, numPts);
		}
	}
}
